package core;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;import java.util.Date;
import java.util.GregorianCalendar;import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Set;

import persistence.TransactionManager;
import persistence.dao.DatabaseDAOFactory;
import persistence.dao.IContainerToProductsDAO;
import persistence.dao.IDAOFactory;
import persistence.dao.IItemsDAO;
import persistence.dao.IProductGroupsDAO;
import persistence.dao.IProductsDAO;
import persistence.dao.IStorageUnitsDAO;
import persistence.dao.SerialDAOFactory;
import persistence.dto.ItemDTO;
import persistence.dto.ProductGroupDTO;
import persistence.dao.DatabaseDAOFactory;
import persistence.dao.IDAOFactory;
import persistence.dao.SerialDAOFactory;
import persistence.dao.StorageUnitsDBDAO;

import com.itextpdf.text.DocumentException;

import core.DataManager;
import core.plugins.PluginRegistry;
import core.reports.ExpiredItemsGuide;
import core.reports.HTMLBuilder;
import core.reports.IBuilder;
import core.reports.IDirector;
import core.reports.IDirectorVisitor;
import core.reports.IVisitor;
import core.reports.N_MonthSupplyGuide;
import core.reports.NoticesGuide;
import core.reports.ProductStatisticsGuide;
import core.reports.RemovedItemsGuide;
import core.reports.TraversalOrder;
import core.reports.N_MonthSupplyGuide;
import core.reports.TraversalOrder;

/**
 * I had to change to implements Serializable instead of implements ModelObject
 * because you can only extend one Class, which we needed to be Observable 
 *
 */
public class CoreFacade extends Observable implements Serializable  {
    
	/** Serializable ID	 */
	private static final long serialVersionUID = -4966230170607105402L;
	/**CoreFacade object. This is a singleton	 */
	private static CoreFacade instance;
	/** ProductManager keeps track of all products in system */
	private ProductManager productManager;
	/** Keeps track of all items in the system	 */
	private ItemManager itemManager;
	/** This is the 'root' for out storageUnit/product group tree (storage unit manager)	 */
	private RootStorageUnit rootStorageUnit;
	/** command history list for undo and redo */
	CommandHistory commandHistory;
	
	private static IDAOFactory factory;
	
	/** The plugin registry for this program */
	//private PluginRegistry pluginRegistry = PluginRegistry.getInstance();
	
	
	/**
	 * File names for serialization saving.
	 */
	
	private static boolean dbMode = false;
    
	
	public IDAOFactory getFactory()
	{
		return factory;
	}
	
	/** 
	 * sets the dbMode flag
	 * @param value true means we will use DB mode
	 */
    public static void setDBMode(boolean value)
    {
    	dbMode = value;
    }
	/**
	 * Singleton wrapper around the constructor for CoreFacade
	 * @return the instance of the coreFacade
	 */
	public static CoreFacade getInstance()
    {
        if(instance == null)
        {
            if(DataManager.isInDatabaseMode())
    			factory = new DatabaseDAOFactory();
    		else
    		{
    			factory = new SerialDAOFactory();
    			instance = factory.loadData();
    		}
    		if(instance == null)
    		{
    			instance = new CoreFacade();
    		}
        }
        return instance;
    }
	
	public static void destroyInstance()
	{
		instance = null;
		
		ProductManager.destroyInstance();
		
		ItemManager.destroyInstance(); 
		
		RootStorageUnit.destroyInstance();
	}
        public void destroyInstance2()
	{
		instance = null;
                productManager = null;
                itemManager = null;
                rootStorageUnit = null;
                commandHistory = null;
                factory = null;
	}
	
	/** recreates the model from the database
	 * 
	 * @return returns true opon success, false otherwise
	 */
	public boolean LoadDatabase()
	{
		productManager.Initialize();
		itemManager.Initialize();
		if(DataManager.isInDatabaseMode())
		{
			factory.loadData();
			itemManager.setLastBarcode();
		}
		
		return true;
	}
	
    /**
     * Constructor for the CoreFacade. Private because this is a singleton.
     */
    private CoreFacade()
    {
        productManager = ProductManager.getInstance();
        itemManager = ItemManager.getInstance();
        rootStorageUnit = RootStorageUnit.getInstance();
        commandHistory = new CommandHistory();
        TransactionManager.initialize();
        /*if(dbMode)
        	factory = new DatabaseDAOFactory();
        else
        	factory = new SerialDAOFactory();
        factory.loadData();*/
    }
    
    public void loadItemIntoContainer(Item i, ProductContainer pc)
    {
    	try
		{
			itemManager.addItemToContainer(i, pc);
		} catch (Exception e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    }
    
    /**
     * Takes all the information from the UI and constructs the necessary data to add an 
     * item
     * @param productBarcode string of the barcode that was scanned in (product)
     * @param entryDate the current date/or specified date of which the item is added
     * @param count the number of this item that is being added - NOT USED
     * @param su storage unit to which the item is being added
     * @param commandGroup the index inside of a batch command if 
     * 		  you are only adding 1 item this would be 0.
     * @param prodAdded true if when adding this item, a product was also added
     * @return the newly created item added to the HIT System, null if failed
     * @throws Exception
     */
    /*public Item loadItem(String itemBarcode, Calendar entryDate, 
    		Calendar exitTime, String prodBarcode, String pcName, int id) throws Exception
    {
    	Product temp = productManager.getProduct(new Barcode(prodBarcode));
    	StorageUnit unit = rootStorageUnit.getStorageUnit(pcName);
    	Item item = new Item(temp, unit, entryDate, new ItemBarcode(prodBarcode));
    	// Set exit time
    	item.setID(id);
    	addItem(item,unit,0,false);
    	return null;
    }*/
    
    
    public Item addItem(String productBarcode, Calendar entryDate, int count, 
    					StorageUnit su, int commandGroup, boolean prodAdded) throws Exception
    {
    	assert(productBarcode != null && entryDate != null && su != null);
    	Barcode pBarcode = new Barcode(productBarcode);
    	Product temp = productManager.getProduct(pBarcode);
    	Item newItem = new Item(temp, su, entryDate, itemManager.generateBarcode());
    	Item added = null;
    	try{
    		added = addItem(newItem,su,commandGroup,prodAdded); 
    	}
    	catch(Exception e)
    	{
    		System.out.println("Exception caught in addItem");
    		return null;
    	}
    	return added;
    }
    
    /**
     * Adds the specified item to the storage unit. If item's product is already somewhere
     * in the storage unit tree, it must be added to the same group in the tree.
     * @param i item to be added
     * @param su storage unit to add the item to
     * @return the newly created item added to the HIT System, null if failed
     * @throws Exception
     */
    public Item addItem(Item i, StorageUnit su, int commandGroup, boolean prodAdded)throws Exception
    {
        assert(i != null);
        assert(su != null);
        /*TransactionManager.begin();
        // This may be done elsewhere
        /*if(prodAdded)
        {
        	IProductsDAO productDAO = factory.getProductsDAO();
        	int prodID = productDAO.add(i.getProduct().createDTO());
        	i.getProduct().setID(prodID);
        }
        IItemsDAO itemDAO = factory.getItemsDAO();
        int itemID = itemDAO.add(i.createDTO());
        i.setID(itemID);
        TransactionManager.end(true); */       
        AddItemCommand addCommand = new AddItemCommand(i, su, commandGroup, prodAdded);
        return commandHistory.execute(addCommand);
    }
   
    public void addItemPersistence(Item i)
    {
    	TransactionManager.begin();
        IContainerToProductsDAO cpDAO = factory.getContsToProdsDAO();
        ProductContainer pc = i.getProductContainer();
        Product p = i.getProduct();
        if(pc instanceof StorageUnit)
        	cpDAO.add(p.createDTO(), ((StorageUnit)pc).createDTO());
    	else
    		cpDAO.add(p.createDTO(), ((ProductGroup)pc).createDTO());
        IItemsDAO itemDAO = factory.getItemsDAO();
        int itemID = itemDAO.add(i.createDTO());
        i.setID(itemID);
        TransactionManager.end(true);
    }
    
    
    public void undoAddItemPersistence(Item item, boolean prodAdded)
    {
    	TransactionManager.begin();
    	boolean productResult = true;
    	boolean itemResult;
    	if(prodAdded)
        {
    		IContainerToProductsDAO cpDAO = factory.getContsToProdsDAO();
    		ProductContainer pc = item.getProductContainer();
            Product p = item.getProduct();
            if(pc instanceof StorageUnit)
            	cpDAO.remove(p.createDTO(), ((StorageUnit)pc).createDTO());
        	else
        		cpDAO.remove(p.createDTO(), ((ProductGroup)pc).createDTO());
            
            IProductsDAO dao = factory.getProductsDAO();
            dao.remove(p.createDTO());
        }
        IItemsDAO itemDAO = factory.getItemsDAO();
        itemResult = itemDAO.remove(item.createDTO());
        TransactionManager.end(productResult && itemResult);
    }
    
    /**
     * Removes the item from the system, and adds to the usedItems
     * @param itemBarcode unique item barcode of which we are removing
     * @return true if it was removed successfully, false otherwise
     * @throws Exception
     */
   /* public Item removeItem(String itemBarcode)throws Exception
    {
    	assert(!itemBarcode.isEmpty());
    	Barcode bCode = new ItemBarcode(itemBarcode);
    	RemoveItemCommand command = new RemoveItemCommand(bCode);
    	return commandHistory.execute(command);
    	/*Item i;
    	try{
    		i = itemManager.removeItem(bCode);
    	}
    	catch(Exception e)
    	{
    		return false;
    	}
    	notify(i);
    	return true;
    }*/
    
    /**
     * Removes the item from the system, and adds to the usedItems
     * @param itemBarcode unique item barcode of which we are removing
     * @return true if it was removed successfully, false otherwise
     * @throws Exception
     */
    public Item removeItem(Item item)throws Exception
    {
    	assert(item != null);
    	RemoveItemCommand command = new RemoveItemCommand(item);
    	return commandHistory.execute(command);
    	/*try{
    		itemManager.removeItem(item);
    	}
    	catch(Exception e)
    	{
    		return false;
    	}
    	return true;*/
    }
    
    /**
     * Transfers items from original product container to a new one, that was
     * specified by the user
     * @param itemBarcode unique item barcode to be moved
     * @param targetPC product container to which the item should be moved
     * @param targetSU storage unit in which the item is being moved to.
     * @throws Exception
     */
    public void transferItem(String itemBarcode, ProductContainer targetPC, 
    						 StorageUnit targetSU)throws Exception
    {
    	assert(!itemBarcode.isEmpty() && targetPC != null && targetSU != null);
    	Barcode bCode = new ItemBarcode(itemBarcode);
    	Item itemToMove = itemManager.getItem(bCode);
    	TransactionManager.begin();
    	IItemsDAO itemDAO = factory.getItemsDAO();
    	ItemDTO iDTO = itemToMove.createDTO();
    	// Find what pc it will be placed in
    	ProductContainer pc = productManager.getContainer(targetSU, itemToMove.getProduct());
    	if(pc == null)
    		iDTO.setParentID(targetSU.getID());
    	else
    		iDTO.setParentID(pc.getID());
    	itemDAO.edit(itemToMove.createDTO());
    	TransactionManager.end(true);
    	
    	Command transferItem = new TransferItemCommand(itemToMove, targetPC, targetSU);
    	commandHistory.execute(transferItem);
    }
    
    /**
     * Transfers items from original product container to a new one, that was
     * specified by the user
     * @param itemBarcode unique item barcode to be moved
     * @param targetPC product container to which the item should be moved
     * @param targetSU storage unit in which the item is being moved to.
     * @throws Exception
     */
    public void transferItem(Item item, ProductContainer targetPC,  
    						 StorageUnit targetSU)throws Exception
    {
    	assert(item != null && targetPC != null && targetSU != null);
//    	Barcode bCode = new Barcode(itemBarcode);
//    	Item itemToMove = itemManager.getItem(bCode);
    	ProductContainer pc = productManager.getContainer(targetSU, item.getProduct());
    	if(pc == null) //Product was not found in target storageUnit
    	{
    		productManager.addProductToContainer(item.getProduct(), targetPC);
    		//itemManager.addItemToContainer(item, targetPC);
    	}
    	else //Product was found in storageUnit
    	{
    		/*if(targetPC.equals(pc)) //product is already in targetPC
    		{
    			//itemManager.addItemToContainer(item, targetPC);
    		}
    		else //Product was in storage unit but in another product container
    		{
    			transferProduct(item.getProduct(), pc, targetPC);
    			//itemManager.addItemToContainer(item, targetPC);
    		}*/
    		if(!targetPC.equals(pc))//Product was in storage unit but in another product container
    		{
    			transferProduct(item.getProduct(), pc, targetPC);
    		}
    	}
    	itemManager.addItemToContainer(item, targetPC);
    }
    
    /**
     * checks if the item can be added to the system
     * @param i the item in question
     * @return true if it IS able to be added, false if it can't.
     */
    public boolean canAddItem(String productBarcode)
    {
    	assert(!productBarcode.isEmpty());
    	if(productBarcode.isEmpty())
    		return false;
    	return true;
    }
    
    /**
     * Checks if the item can be removed
     * @param i the item to remove
     * @return true if it can be removed, false if it can'
     */
    public boolean canRemoveItem(String itemBarcode)
    {
    	assert(!itemBarcode.isEmpty());
        return !itemBarcode.isEmpty();
    }
    
    /**
     * Adds a product to our system, to the specified product container
     * @param p product to be added
     * @param pc location to where this product will be added
     * @throws Exception
     */
    public void addProduct(Product p, ProductContainer pc)throws Exception
    {
    	assert(p != null);
    	assert(pc != null);
    	
    	TransactionManager.begin();
    	IProductsDAO dao = factory.getProductsDAO();
    	IContainerToProductsDAO dao2 = factory.getContsToProdsDAO();
    	int id = dao.add(p.createDTO());
    	boolean commit = true;
    	if(id <= 0)
    		commit = false;
    	p.setID(id);
    	if(pc instanceof StorageUnit)
    	{
    		dao2.add(p.createDTO(), ((StorageUnit)pc).createDTO());
    	}
    	else
    		dao2.add(p.createDTO(), ((ProductGroup)pc).createDTO());
    	
    	TransactionManager.end(commit);
    	if(commit == false)
    		return;
    	productManager.addProductContainer(pc);
    	itemManager.addProductContainer(pc);
    	productManager.addProduct(p);
        productManager.addProductToContainer(p, pc);
    }
    
    /**
     * Adds a product to our system, to the specified product container
     * @param p product to be added
     * @param pc location to where this product will be added
     * @throws Exception
     */
    public void loadProduct(Product p, ProductContainer pc, int id)throws Exception
    {
    	assert(p != null);
    	assert(pc != null);
    	p.setID(id);
    	//TransactionManager.end(true);
    	productManager.addProductContainer(pc);
    	itemManager.addProductContainer(pc);
    	productManager.addProduct(p);
        productManager.addProductToContainer(p, pc);
        //TransactionManager.begin();
    }
    
    
    /**
     * This method adds the specific Product to the system.
     * 
     * @param p
     * @throws Exception
     */
    public void addProduct(Product p)throws Exception
    {
    	assert (p != null);
    	TransactionManager.begin();
    	IProductsDAO dao = factory.getProductsDAO();
    	int id = dao.add(p.createDTO());
    	boolean commit = true;
    	if(id <= 0)
    		commit = false;
    	p.setID(id);
    	
    	TransactionManager.end(commit);
    	productManager.addProduct(p);
    }
    
    /**
     * Removes a product from the system
     * @param p product to remove
     * @throws Exception
     */
    public void deleteProduct(Product p)throws Exception
    {
    	assert(p != null);
    	
    	TransactionManager.begin();
    	IProductsDAO dao = factory.getProductsDAO();
    	boolean commit =dao.remove(p.createDTO());
    	TransactionManager.end(commit);
    	if(!commit)
    		throw new Exception("Couldn't delete product from database");
        if(!productManager.deleteProduct(p))
        	System.out.println("Error removing Product:" 
        			+ p.getDescription() + " from System");
        	
    }	
    
    /**
     * Checks to see if the product can be added
     * @param p product in question
     * @throws Exception 
     */
    public boolean canAddProduct(Product p) throws Exception
    {
    	assert(p != null);
    	if(p == null)
    		throw new Exception("null Product p");
        return productManager.canAdd(p);
    }
    
    /**
     * Checks to see if the product can be removed from pc
     * @param p product to remove
     * @param pc product container to search for and remove product instance
     */
    public boolean canDeleteProductFromContainer(Product p, ProductContainer pc)
    {
    	assert p != null;
    	assert (pc != null);
    	Set<Item> itemsOfType;
		try
		{
			itemsOfType = itemManager.getItemsFromContainer(pc);
			if (itemsOfType == null) return true;
			Iterator<Item> iter = itemsOfType.iterator();
			while(iter.hasNext())
			{
				Item temp = iter.next();
				if(temp.getProduct().equals(p))
					return false;
			}
			
	    	
	    	return true;
		} catch (Exception e)
		{
			notify(e.getMessage());
			e.printStackTrace();
		}
		return false;
    	
    	
    }
    
    /**
     * Checks to see if the product can be removed from pc
     * @param p product to remove
     */
    public boolean canDeleteProduct(Product p)
    {
    	assert p != null;
    	
    	Set<Item> itemsOfType = itemManager.getItems(p);
    	
    	if (itemsOfType == null) return true;
    	
    	if (itemsOfType.size() > 0) return false;
    	
    	return true;
    }
    
    /**
     * Finds the parent StorageUnit where the ProductContainer is located
     * @param pc ProductContainer to traverse to find parent
     * Storage Unit
     * @return returns the parent StorageUnit
     * @throws Exception
     */
    public StorageUnit getParentStorageUnit(ProductContainer pc)
    {
    	assert(pc != null);
    	if(pc == null)
    		return null;
    	if(pc instanceof StorageUnit)
    		return (StorageUnit)pc;
    	if(pc instanceof ProductGroup)
		{
			ProductGroup pg = (ProductGroup)pc; 
			while(pg.getParentContainer() instanceof ProductGroup)
			{
				pg = (ProductGroup)pg.getParentContainer();
			}
			return (StorageUnit)pg.getParentContainer();
		}
		return null;
    }
    
    /**
     * Removes a product from a ProductContainer
     * @param p product to be removed
     * @param pc where the product is to be removed
     */
    public boolean deleteProductFromContainer(Product p, ProductContainer pc)
    {
    	boolean result = productManager.deleteProductFromContainer(p, pc);
    	if(!result)
    	{
    		notify("Failed to remove " + p.getDescription() + " from " + pc.getName());
    		return false;
    	}
    	return true;
    }
    
    /**
     * This method transfers products, and consequently items from one ProductContainer
     * to another
     * @param p the product to be transferred
     * @param fromPC the container where the product is being transferred from
     * @param targetPC the container where the product is to be transferred to
     * @throws Exception thrown if any operation fails, with informative message
     */
    public void transferProduct(Product p, 
            ProductContainer fromPC, ProductContainer targetPC)throws Exception
    {
    	assert(p != null);
        
    	assert(targetPC != null);
    	assert(fromPC != null);
    	if(p == null || targetPC == null || fromPC == null)
    		throw new Exception("Null arguments passed in");
    	
    	//we need to check if targetPC and fromPC belong to the same StorageUnit
    	StorageUnit fromSU = getParentStorageUnit(fromPC);
    	StorageUnit targetSU = getParentStorageUnit(targetPC);
    	
    	if(fromSU.equals(targetSU))
    	{
    		// targetPC and fromPC are in the same StorageUnit
        	// now we want to remove the product from fromPC and add it to targetPC
    		boolean result = productManager.deleteProductFromContainer(p, fromPC);
    		if(result == false)
    			throw new Exception("Error occurred trying to transfer product out of container");
    		result = productManager.addProductToContainer(p, targetPC);
    		//addProduct(p, targetPC);
    		if(result == false)
    			throw new Exception("Error occurred trying to transfer product into container");
    		// get the list of all items in fromPC ProductContainer
    		Set<Item> items = new HashSet<Item>(itemManager.getItemsFromContainer(fromPC));
    		if(items != null)
    		{
    			Iterator<Item> iter = items.iterator();
    			while(iter.hasNext())
    			{
    				Item item = iter.next();
	    			// check if item is of the same type as the product to transfer
	    			if(item.getProduct().equals(p))
	    			{
	    				// we need move item from fromPC to targetPC
	    				itemManager.addItemToContainer(item, targetPC);
	    			}
    			}
    		}
    	}// end fromSU.equals(targetSU)
    	else
    	{
    		// targetPC and fromPC are in different StorageUnits
    		// we need to check where the product is at in targetSU, if anywhere
    		Set<Product> products = productManager.getAllProductsFromStorageTree(targetSU);
    		// using a List we can just call contains
    		Iterator<Product> targetIter = products.iterator();
    		boolean exists = false;
    		while(targetIter.hasNext())
    		{
    			Product prod = targetIter.next();
    			// if we found the same product in the Target Storage Unit
    			// then we know where to put all of the items.
    			// and the product itself
    			if(prod.equals(p))
    			{
    				System.out.println("product exists");
    				// this will return the container where the product is found
    				ProductContainer oldPC = productManager.getContainer(targetSU, prod);
    				if(oldPC.equals(targetPC))
    				{
    					System.out.println("No moving necessary");
    					return;
    				}
    				// put product in targetPC first
    				productManager.addProductContainer(targetPC);
        	    	itemManager.addProductContainer(targetPC);
    	    		boolean result = productManager.addProductToContainer(p, targetPC);
    	    		if(result == false)
    	    			throw new Exception("Error occurred trying to transfer " +
    	    					"product into container");
    				// this will give us the items we need to move to the new container
    				Set<Item> items = new HashSet<Item>(itemManager.getItemsFromContainer(oldPC));
    				// transfer items from oldPC in the targetSU, to targetPC
    				Iterator<Item> iter = items.iterator();
    				while(iter.hasNext())
    				{
    					Item item = iter.next();
    					if(item.getProduct().equals(p))
    					{
    						// we need move item from oldPC to targetPC
    	    				//itemManager.removeItem(item);
    	    				// the previous call adds item to removedItems list
    	    				// we only wanted to transfer it, so remove it from the
    	    				// removedItems list
    	    				//itemManager.getRemovedItems().remove(item);
    	    				itemManager.addItemToContainer(item, targetPC);
    					}
    				}
    				
    	    		// set exists so we know we found product in targetSU
    				exists = true;
    			
    				// now we want to remove the product from oldPC
    				result = productManager.deleteProductFromContainer(p, oldPC);
    				if(!result)
    					throw new Exception("Error removing " + p.getDescription() 
    							+ " from " + oldPC.getName());
    				
    				
    			}
    		}
    		if(!exists)//product wasn't found in targetSU
    		{
    			// then we need to just manually add the product to targetPC
    			System.out.println("putting "+p.getDescription()+ " into " + targetPC.getName());
    			//notify("putting "+p.getBarcodeString()+ " into " + targetPC.getName());
    			productManager.addProductContainer(targetPC);
    	    	itemManager.addProductContainer(targetPC);
    			productManager.addProductToContainer(p, targetPC);
    		}
    	}// end fromSU not equal to (targetSU)
    }
    
    public void transferProductToContainer(Product p, ProductContainer targetPC) throws Exception
    {
    	StorageUnit targetSU = getParentStorageUnit(targetPC);
    	Set<Product> products = productManager.getAllProductsFromStorageTree(targetSU);
		// using a List we can just call contains
		Iterator<Product> targetIter = products.iterator();
		boolean exists = false;
		while(targetIter.hasNext())
		{
			Product prod = targetIter.next();
			// if we found the same product in the Target Storage Unit
			// then we know where to put all of the items.
			// and the product itself
			if(prod.equals(p))
			{
				
    			
				System.out.println("product exists");
				// this will return the container where the product is found
				ProductContainer oldPC = productManager.getContainer(targetSU, prod);
				// put product in targetPC first
				productManager.addProductContainer(targetPC);
    	    	itemManager.addProductContainer(targetPC);
	    		boolean result = productManager.addProductToContainer(p, targetPC);
	    		if(result == false)
	    			throw new Exception("Error occurred trying to transfer product into container");
				// this will give us the items we need to move to the new container
				Set<Item> items = new HashSet<Item>(itemManager.getItemsFromContainer(oldPC));
				// transfer items from oldPC in the targetSU, to targetPC
				Iterator<Item> iter = items.iterator();
				while(iter.hasNext())
				{
					Item item = iter.next();
					if(item.getProduct().equals(p))
					{
						// we need move item from oldPC to targetPC
	    				itemManager.addItemToContainer(item, targetPC);
					}
				}
				
	    		// set exists so we know we found product in targetSU
				exists = true;
			
				// now we want to remove the product from oldPC
				result = productManager.deleteProductFromContainer(p, oldPC);
				if(!result)
					throw new Exception("Error removing " + p.getDescription() 
							+ " from " + oldPC.getName());
				
				
			}
		}
		if(!exists)//product wasn't found in targetSU
		{
			// then we need to just manually add the product to targetPC
			System.out.println("putting "+p.getDescription()+ " into " + targetPC.getName());
			//notify("putting "+p.getBarcodeString()+ " into " + targetPC.getName());
			productManager.addProductContainer(targetPC);
	    	itemManager.addProductContainer(targetPC);
			productManager.addProductToContainer(p, targetPC);
			//addProduct(p, targetPC);
		}
    }
    
    /**updates the entrydate and the exit
     * @return true if successful
     */
    public boolean editItem(Item i, Calendar newEntryDate)throws Exception
    {
        assert(i != null);
        assert(newEntryDate != null);
        
        i.setEntryDate(newEntryDate);
        i.editExpirationDate();

        TransactionManager.begin();
        IItemsDAO dao = factory.getItemsDAO();
        boolean commit = dao.edit(i.createDTO());
        TransactionManager.end(commit);
        
        i.setUpdated(true);
        notify(i);
        
		return true;
    }
    
    public void editProduct(Product before, Product after)throws Exception
    {
    	assert(before != null);
        assert(after != null);
        after.setID(before.getID());
        
        TransactionManager.begin();
    	IProductsDAO dao = factory.getProductsDAO();
    	boolean commit = dao.edit(after.createDTO());
    	
    	TransactionManager.end(commit);
    	if(!commit)
    	{
        	System.out.println("Failed to edit product in database");
        	return;
    	}
        
        productManager.editProduct(before, after);
        before.setUpdated(true);
        notify(before);
        // Check that itemManager gets updated as well (deep copy vs reference)
    }
    
    /**
     * Updates the storage unit with nameBefore to be nameAfter
     * @param nameBefore the storage unit to be edited
     * @param nameAfter the new name for the storage unit
     * @return true if the update was done with no problems, false if it failed
     */
    public boolean editStorageUnit(String nameBefore, String nameAfter)
    {
    	assert(!nameBefore.isEmpty());
    	assert(!nameAfter.isEmpty());
    	try{
    		StorageUnit su = rootStorageUnit.editStorageUnit(nameBefore, nameAfter);
    		TransactionManager.begin();
    		IStorageUnitsDAO dao = factory.getSUsDAO();
    		boolean result = dao.edit(su.createDTO());
    		TransactionManager.end(result);
    		su.setUpdateFlag(true);
    		notify(su);
    	}
    	catch(Exception e)
    	{
    		e.printStackTrace();
    		return false;
    	}
    	return true;
    }
    
    public void editProductGroup(ProductGroup before, ProductGroup after)throws Exception
    {
        assert(before != null);
        assert(after != null);
        
        after.setID(before.getID());
        TransactionManager.begin();
		IProductGroupsDAO dao = factory.getPGsDAO();
		boolean result = dao.edit(after.createDTO());
		TransactionManager.end(result);
		if(result == false)
		{
			System.out.println("Error Editing Product Group in database");
			throw new Exception("Couldn't edit Produt Group in the database");
		}
		
        before.editProductGroup(after);
        before.setUpdateFlag(true);
        notify(before);
    }
    
    /**
     * Gets all the products in a particular productContainer
     * @param pc the product container we are interested in
     * @return a collection of all products in pc
     * @throws Exception 
     */
    public Set<Product> getAllProducts(ProductContainer pc)
    {
    	assert(pc != null);
		return productManager.getAllProductsInContainer(pc);
			
    }
    
    /**
     * Gets all the products in the root (every product that is in the system)
     * @return a collection of all products in the system
     */
    public List<Product> getAllProductInRoot()
    {
        return productManager.getAllProducts();
    }
    
    /**
     * Retreives all the items stored inside of the particular product container
     * @param pc the product container we want all items in
     * @return a collection of all the current items in pc
     * @throws Exception 
     */
    public Set<Item> getAllItems(ProductContainer pc)
    {
    	assert(pc != null);
    	Set<Item> items;
		try
		{
			items = (Set<Item>)itemManager.getAllItemsInPC(pc);
			if(items == null)
	    		return new HashSet<Item>();
	    	return items;
		} catch (Exception e)
		{
			e.printStackTrace();
			return new HashSet<Item>();
		}
    	
    }
    
    /**
     * Retrieves all current items contained in the root storage unit (also known as 
     * the whole system.
     * 
     * @return A collection of all current items in the Root
     */
    public Collection<Item> getAllItemsInRoot()
    {
    	return itemManager.getAllItems();
    }
    
    /**
     * This will load the data from the file at the start of the program
     * @throws Exception 
     */
    public void loadDataFromMemory() throws Exception
    {
    	try{
    		System.out.println("Not Called");
	    	//productManager = (ProductManager) DataManager.loadAtStart(pmFile);
	    	//itemManager = (ItemManager) DataManager.loadAtStart(imFile);
	    	//rootStorageUnit = (RootStorageUnit) DataManager.loadAtStart(rsuFile);
    	}
    	catch(Exception e)
    	{
    		itemManager.setLastDate(null);
    		throw e;
    	}
    }
    
    /**
     * whenever needed this will save the HIT Program to memory 
     * @throws IOException
     */
    public void saveData() throws IOException
    {
    	//DataManager.saveAtClose(this, "SavedData.ser");
    	//old code above
    	// new code
    	factory.saveData();
    	
    }
    
    /**
     * Adds a storage unit to the root
     * @param suName name to be added
     * @return true if it was successful
     * @throws Exception 
     */
    public StorageUnit addStorageUnit(String suName)throws Exception
    {
    	assert(!suName.isEmpty());
    	StorageUnit unit = new StorageUnit(suName);
    	TransactionManager.begin();
    	IStorageUnitsDAO dao = factory.getSUsDAO();
    	int id = dao.add(unit.createDTO());
    	if(id == -5)//Error
    	{
    		TransactionManager.end(false);
    		throw new Exception("Error in Database function");
    	}
    	TransactionManager.end(true);
    	if(id <= 0)
    		rootStorageUnit.addStorageUnit(unit,rootStorageUnit.generateID());
    	else
    		rootStorageUnit.addStorageUnit(unit,id);
    	
    	productManager.addProductContainer(unit);
    	itemManager.addProductContainer(unit);
    	
    	notify(unit);
    	return unit;
    }
    
    public StorageUnit loadStorageUnit(String name, int ID) throws Exception
    {
    	StorageUnit unit = new StorageUnit(name);
    	unit.setID(ID);
    	unit.setPCID(ID);
    	
    	rootStorageUnit.addStorageUnit(unit);
    	productManager.addProductContainer(unit);
    	itemManager.addProductContainer(unit);
    	return unit;
    }
    
    /**
     * Checks if the storage unit with the given name can be added
     * @param suName name to check
     * @return true if it can be added, false otherwise
     */
    public boolean canAddStorageUnit(String suName)
    {
    	//assert(!suName.isEmpty());
    	StorageUnit unit = new StorageUnit(suName);
    	return rootStorageUnit.canAddStorageUnit(unit);
    }
    
    /**
     * Removes the given storage unit from root
     * @param su the storage unit to be removed
     * @return
     * @throws Exception 
     */
    public boolean deleteStorageUnit(StorageUnit su)
    {
    	assert(su != null);
    	try{
	    	if(canDeleteStorageUnit(su))
	    	{
	    		TransactionManager.begin();
	    		IStorageUnitsDAO dao = factory.getSUsDAO();
	    		IContainerToProductsDAO dao2 = factory.getContsToProdsDAO();
	    		boolean result = dao.remove(su.createDTO());
	    		boolean result2 = dao2.remove(su.createDTO());
	    		TransactionManager.end(result);
	    		if(rootStorageUnit.deleteStorageUnit(su))
	    		{
	    			notify(su);
	    			return true;
	    		}
	    	}
	    	return false;
    	}
    	catch (Exception e)
    	{
    		return false;
    	}
    }
    
    /**
     * Checks if the storage unit can be removed
     * @param su the storage unit to remove
     * @return true if it can be removed.
     * @throws Exception 
     */
    public boolean canDeleteStorageUnit(StorageUnit su)
    {
    	assert(su != null);
    	
    	return canDeleteProductContainerRecursive(su);
    	
    }
    
    private boolean canDeleteProductContainerRecursive(ProductContainer pc)
    {
    	Collection<Item> collection = null;
		try 
		{
			collection = itemManager.getAllItemsInPC(pc);
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
		}
		
    	if (collection == null)
    	{
    		//empty check children
    		List<ProductGroup> children = pc.getProductGroups();
    		for (int i = 0; i < children.size(); i++)
    		{
    			if (!canDeleteProductContainerRecursive(children.get(i)))
    			{
    				return false;
    			}
    		}
    		return true;
    	}
    	else if (collection.isEmpty())
    	{
    		//empty check children
    		List<ProductGroup> children = pc.getProductGroups();
    		for (int i = 0; i < children.size(); i++)
    		{
    			if (!canDeleteProductContainerRecursive(children.get(i)))
    			{
    				return false;
    			}
    		}
    		return true;
    	}
    	else
    	{
    		return false;
    	}
    }
    
    /**
     * Adds a product group to the storage unit tree
     * @param parent parent container of the group we are adding
     * @param pgName name of the new product group
     * @param size associated with the three month supply
     * @param unit unit type (kg, ounces, count, etc)
     * @return the newly added ProductGroup if it was successful
     * @throws Exception 
     */
    public ProductGroup addProductGroup(ProductContainer parent, 
    		String pgName, float size, Unit unit) throws Exception
    {
    	assert(parent != null);
    	assert(!pgName.isEmpty());
    	assert(size > -1);
    	boolean commit = true;
    	Amount amt = new Amount(unit, size);
    	// Do DB Stuff
    	TransactionManager.begin();
    	ProductGroup group = new ProductGroup(pgName, amt, parent);
    	ProductGroupDTO dto = group.createDTO();
    	IProductGroupsDAO dao = factory.getPGsDAO();
    	int id = dao.add(dto);
    	if(id <= 0)
    	{
    		// Running is Serialization Mode
    		group.setPCID(rootStorageUnit.generateID());
    	}
    	else
    	{
    		// Running id DB Mode
    		group.setPCID(id);
    		group.setID(id);
    	}
    	TransactionManager.end(commit);
    	// Do Model Stuff
		parent.addProductGroup(group);    	
		productManager.addProductContainer(group);
    	itemManager.addProductContainer(group);
    	notify(group);
    	
    	return group;
    }
    
    /**
     * Adds a product group to the storage unit tree
     * @param parent parent container of the group we are adding
     * @param pgName name of the new product group
     * @param size associated with the three month supply
     * @param unit unit type (kg, ounces, count, etc)
     * @return the newly added ProductGroup if it was successful
     * @throws Exception 
     */
    public ProductGroup loadProductGroup(ProductContainer parent, 
    		String pgName, float size, Unit unit, int _id) throws Exception
    {
    	assert(parent != null);
    	assert(!pgName.isEmpty());
    	assert(size > -1);
    	Amount amt = new Amount(unit, size);
    	ProductGroup group = new ProductGroup(pgName, amt, parent);
    	group.setID(_id);
    	group.setPCID(_id);
		parent.addProductGroup(group);    	
		productManager.addProductContainer(group);
    	itemManager.addProductContainer(group);
    	
    	return group;
    }
    
    /**
     * Adds a product group to the storage unit tree
     * @param parent parent container of the group we are adding
     * @param pgName name of the new product group
     * @param size associated with the three month supply
     * @param unit unit type (kg, ounces, count, etc)
     * @return true if item could be added
     */
    public boolean canAddProductGroup(ProductContainer parent, String pgName, float size, Unit unit)
    {
    	assert(parent != null);
    	//assert(!pgName.isEmpty());
    	//assert(size > -1);
    	
    	Amount amt = new Amount(unit,size);
    	if(!amt.isValidAmount())
    		return false;
    	ProductGroup pg;
    	try{
    		pg = new ProductGroup(pgName, amt, parent);
    	} catch(Exception e)
    	{
    		return false;
    	}
    	return parent.canAddProductGroup(pg);
    }
    
    /**
     * Removes the productGroup from the parent product group
     * @param toRemove the group to removed
     * @param parent the parent of the removed group
     * @return true if it was successful
     * @throws Exception 
     */
    public boolean deleteProductGroup(ProductGroup toRemove, ProductContainer parent)
    {
    	assert(toRemove != null);
    	assert(parent != null);
    	
    	if(canDeleteProductGroup(toRemove, parent))
    	{
    		if(parent.deleteProductGroup(toRemove))
    		{
    			notify(toRemove);
    			return true;
    		}
    		return false;
    	}
    	return false;
    }
    
    /**
     * Checks if a productgroup can be removed from the parent product group
     * @param toRemove the group to removed
     * @param parent the parent of the removed group
     * @return true if it was successful
     * @throws Exception 
     */
    public boolean canDeleteProductGroup(ProductGroup toRemove, 
    					ProductContainer parent)
    {
    	assert(toRemove != null);
    	assert(parent != null);
    	
    	return canDeleteProductContainerRecursive(toRemove);
    	/*try{
	    	if(!parent.canDeleteProductGroup(toRemove)) // is toRemove in parent
	    	{
	    		return false;
	    	}
	    	// it is in there, we need to check if it is empty
	    	Collection<Item> collection =  itemManager.getAllItemsInPC(toRemove);
	    	if(collection.isEmpty())
	    		return true;
	    	else
	    		return false;
    	}
    	catch(Exception e)
    	{
    		return false;
    	}*/
    }
    
    /**
     * Queries the appropriate maps to determine if the product is in the system
     * @param p Product to be searched for
     * @return returns true if the product is found
     */
    public boolean productExists(Product p)
    {
    	return true;
    }
    
    /**
     * This returns the List of StorageUnits
     * 
     */
    public List<StorageUnit> getStorageUnits()
    {
    	return rootStorageUnit.getStorageUnits();
    }
    
    /**
     * Returns the number of occurrences of a product in a ProductContainer
     * @param p
     * @param pc
     * @return
     */
    public int getCountOfProdInProductContainer(Product p, ProductContainer pc)
    {
    	try
		{
			Set<Item> items = itemManager.getItemsFromContainer(pc);
			Iterator<Item> iter = items.iterator();
			int count = 0;
			while(iter.hasNext())
			{
				Item item = iter.next();
				if(item.getProduct().equals(p))
					count++;
				
			}
			return count;
			
		} catch (Exception e)
		{
			e.printStackTrace();
		}
    	return 0;
    }
    
    /**
     * This is a public method used to notify observers of any changes
     * @param hint a hint to the observers indicating what happened
     */
    public void sendNotify(Object hint)
    {
    	notify(hint);
    }
    
    /**
     * This gets called internally to notify all observers of change
     * @param hint a hint to the observers indicating what happened
     */
    private void notify(Object hint) 
    {
//	 	try{
	    	//saveData();
			this.setChanged();
			this.notifyObservers(hint);
//    	}
//    	catch(IOException e)
//    	{
//    		
//    	}
	}
    
    public void callNotify(Object hint)
    {
    	notify(hint);
    }
    /**
     * Checks to see if the passed in date is a valid date.  This means
     * that is cannot be in the future or before 1/1/2000. This method
     * returns true if the date is valid, Otherwise false is returned.
     * 
     * @param d The Date that we want to check
     * @return boolean True if the date passed in is valid. false if it's not.
     */
    public boolean isValidDate(Date d)
    {
    	Date earliest = new GregorianCalendar(2000, 0, 1).getTime();
    	Date latest = new Date();
    	
    	if (d.before(earliest))
    	{
    		return false;
    	}
    	
    	if (d.after(latest))
    	{
    		return false;
    	}
    	
    	return true;
    }
    
    /**
     * Returns the Product with the specified Barcode... If the Product
     * does not exist or the product is not in the system then this 
     * method will return null.
     * 
     * @param barcode  A string representing the barcode of the Product.
     * @return A Product associated with that barcode or null if the
     * 		Product does not exist.
     */
    public Product getProduct(String barcode)
    {
    	Barcode pBarcode = new Barcode(barcode);
    	
    	Product p = productManager.getProduct(pBarcode);
  
    	return p;
    }
    /**
     * first checks if an item exists, if it does then returns it
     * otherwise returns null
     * @param bCode the barcode of an item to see if it exists
     * @return item if item with that barcode exists
     */
    public Item getItem(String barcode)
    {
        if(!itemExist(barcode))
        {
            return null;
        }
        
        return itemManager.getItem(barcode);
    }
    
    
    private boolean itemExist(String bCode)
    {
        Barcode i = new ItemBarcode(bCode);
        try{
            if(itemManager.getItem(i) == null)
            {
                return false;
            }
        }
        catch(Exception e)
        {
            return false;
            //refactor these exceptions out...
        }
        
        return true;
    }
    
    public String generateBarcodes(List<Item> items)
    {
    	try 
    	{
			return ItemBarcodeGenerator.generateBarcodePDF(items);
		} 
    	catch (FileNotFoundException e) 
    	{
			e.printStackTrace();
		} 
    	catch (DocumentException e) 
    	{
			e.printStackTrace();
		}
    	
    	return "";
    }
    
    /**
	 * undoes the last command executed
	 */
	public Item undo()
	{
		return commandHistory.undo();
	}
	
	/**
	 * re-does the last command that was undone
	 */
	public Item redo()
	{
		return commandHistory.redo();
	}
    
    /** 
	 * determines whether doing an undo operation is legal
	 * @return true if undo is valid, false otherwise
	 */
	public boolean canUndo()
	{
		return commandHistory.canUndo();
	}
	
	/** 
	 * determines whether doing a redo operation is legal
	 * @return true if redo is valid, false otherwise
	 */
	public boolean canRedo()
	{
		return commandHistory.canRedo();
	}
	
	/**
     * clears the CommandHistory buffer
     */
    public void clearHistory()
    {
    	commandHistory.clearHistory();
    }
    
    public ProductContainer getRemovedItemPC()
    {
    	return commandHistory.getRemovedItemPC();
    }
    
    /**
     * creates a report type specified by the report parameter
     * @param report the type of report to create
     * @return returns a the created reports filename
     */
        
    public String createReport(String nameOfReport, String nameOfBuilder, int months)
    {
        String filePath = "";
        IDirectorVisitor guide;
        
        if(nameOfReport.equals(ReportConstants.EXPIRED_ITEMS_REPORT))
        {
            guide = new ExpiredItemsGuide();
            
            if(nameOfReport.equals(ReportConstants.EXPIRED_ITEMS_REPORT))
            {
                guide = new ExpiredItemsGuide();
                rootStorageUnit.accept(guide, TraversalOrder.PreOrder);
            }
            else if(nameOfReport.equals(ReportConstants.NMONTH_SUPPLY_REPORT))
            {
                guide = new N_MonthSupplyGuide(3,nameOfBuilder);
                rootStorageUnit.accept(guide, TraversalOrder.PreOrder);
            }
            else if(nameOfReport.equals(ReportConstants.NOTICES_REPORT))
            {
                guide = new NoticesGuide();
                rootStorageUnit.accept(guide, TraversalOrder.PreOrder);
            }
            else if(nameOfReport.equals(ReportConstants.PRODUCT_STATISTICS_REPORT))
            {
                guide = new ProductStatisticsGuide(months);
            }
            else
            {
                guide = null;
                return "error";
            }
            
            
            filePath = guide.construct(nameOfBuilder);
            
            
            return filePath;
            
        }
        else if(nameOfReport.equals(ReportConstants.NMONTH_SUPPLY_REPORT))
        {
            guide = new N_MonthSupplyGuide(months, nameOfBuilder);
            rootStorageUnit.accept(guide, TraversalOrder.PreOrder);
        }
        else if(nameOfReport.equals(ReportConstants.PRODUCT_STATISTICS_REPORT))
        {
            guide = new ProductStatisticsGuide(months);
        }
        else
        {
            guide = null;
            return "error";
        }
        
        
        filePath = guide.construct(nameOfBuilder);
        
        
        return filePath;
        
    }
    
    public String createRemovedItemsReport(String nameOfBuilder, Calendar fromDate)
    {
    	itemManager.setLastDate(new GregorianCalendar());
        String filePath = "";
        IDirectorVisitor guide;
        guide = new RemovedItemsGuide(fromDate);
        filePath = guide.construct(nameOfBuilder);
        return filePath;
        
    }
    
    public Calendar getLastReportDate()
    {
    	return itemManager.getLastDate();
    }
    
    /**
     * undo the item added to the system, including the product if a new one
     * was added in the process
     * @param i the item to be removed
     */
    public void undoAddItem(Item i)
    {
    	
    }
    
    /**
     * undo the action of deleting the item
     * @param i the item to be un deleted
     */
    public void undoDeleteItem(Item i)
    {
    	
    }
    
    /**
     * undo the transferring of an item
     * @param i the item to be moved back to original location
     */
    public void undoTransferItem(Item i)
    {
    	
    }
    
    public ProductManager getPM()
    {
    	return productManager;
    }
    
    public ItemManager getIM()
    {
    	return itemManager;
    }
    
    public Map<Product, List<Item>> getHistoryMap()
    {
    	return commandHistory.getMap();
    }
    
    public List<Product> getHistoryProductList()
    {
    	return commandHistory.getList();
    }
    
    /**
     * Creates the N-Month Supply Report and returns a file name where the 
     * file is stored.
     * 
     * @param numberOfMonths How many months is the report for.
     * @param format Which format will the report be in. HTML or PDF.
     * @return The file name of the report.
     */
    public String createThreeMonthSupply(int numberOfMonths, String format)
    {
    	N_MonthSupplyGuide newGuide = new N_MonthSupplyGuide(numberOfMonths, format);
    	
    	rootStorageUnit.accept(newGuide, TraversalOrder.PostOrder);
    	rootStorageUnit.accept(newGuide,  TraversalOrder.PreOrder);
    	
		return newGuide.construct("");	
    }
    
    /**
     * Create Notices report and returns the filename of the
     * report.
     * 
     * @param formatWhich format will the report be in. HTML or PDF.
     * @return The file name of the report
     */
    public String createNoticesReport(String format)
    {
    	NoticesGuide newGuide = new NoticesGuide();
    	
    	rootStorageUnit.accept(newGuide, TraversalOrder.PostOrder);
    	rootStorageUnit.accept(newGuide, TraversalOrder.PreOrder);
    	
    	return newGuide.construct(format);
    }
    
    /**
     * Gets the number of items for the passed in product.
     * 
     * @param p The product who we want to get the number of
     * 		items associated with it.
     * @return An integer containing the number of items of 
     * 		that product type in the system.
     */
    public int getNumItemsInSystem(Product p)
    {
    	Set<Item> itemsOfproduct = itemManager.getItems(p);
    	
    	if (itemsOfproduct == null) return 0;
    	
    	return itemsOfproduct.size();
    }
    
    /**
     * This will take a string representing a barcode and will
     * then search the database plugins for the description
     * of the product.  This method will then return the
     * description of the product or null if the product 
     * was not found.
     * 
     * @param barcode A String representing the barcode number
     * 		of the product we are itentifying.
     * 
     * @return A String holding the description of the product.  
     * 		If the product was not found or identified then
     * 		null is returned.
     */
    public String idProduct(String barcode)
    {
    	try
    	{
    		return PluginRegistry.getInstance().getProductIDPlugin().idProduct(barcode);
    	}
    	catch (Exception e)
    	{
    		System.out.println("Plugin Error : Check Internet Connection");
    		return null;
    	}
    	//return null;
    }
    
    public void addRemovedItem(Item i)
    {
    	itemManager.addRemovedItem(i);
    	
    }
    
    public ProductStatisticsGuide testProductStatisticsGuide(int months)
    {
    	ProductStatisticsGuide testguide = new ProductStatisticsGuide(months);
    
    	testguide.construct(ReportConstants.PDF_BUILDER);
    	
    	return testguide;
    
    	//filePath = guide.construct(nameOfBuilder);
    }
}


